package com.hitomi.smlibrary;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

/**
 * Created by hitomi on 2016/9/13. <br/>
 * <p>
 * github : https://github.com/Hitomis <br/>
 * <p>
 * email : 196425254@qq.com
 */
public class SpinMenuLayout extends ComponentContainer implements Component.ClickedListener, Component.TouchEventListener, Component.EstimateSizeListener, Component.LayoutRefreshedListener {

    /**
     * View 之间间隔的角度
     */
    private static final int ANGLE_SPACE = 45;

    /**
     * View 旋转时最小转动角度的速度
     */
    private static final int MIN_PER_ANGLE = ANGLE_SPACE;

    /**
     * 用于自动滚动时速度加快，无其他意义
     */
    private static final float ACCELERATE_ANGLE_RATIO = 1.8f;

    /**
     * 用于加长半径，无其他意义
     */
    private static final float RADIUS_HALF_WIDTH_RATIO = 1.2f;

    /**
     * 转动角度超出可转动范围时，转动角度的迟延比率
     */
    private static final float DELAY_ANGLE_RATIO = 5.6f;

    /**
     * 点击与拖动的切换阀值
     */
    private final int touchSlopAngle = 2;

    /**
     * 最小和最大惯性滚动角度值 [-(getChildCount() - 1) * ANGLE_SPACE, 0]
     */
    private int minFlingAngle, maxFlingAngle;

    /**
     * delayAngle: 当前转动的总角度值， perAngle：每次转动的角度值
     */
    private float delayAngle, perAngle;

    /**
     * 半径：从底边到 Child 高度的中点
     */
    private float radius;

    /**
     * 每次手指按下时坐标值
     */
    private float preX, preY;

    /**
     * 每次转动的速度
     */
    private float anglePerSecond;

    /**
     * 每次手指按下的时间值
     */
    private long preTimes;

    /**
     * 是否可以循环滚动
     */
    private boolean isCyclic;

    /**
     * 是否允许可以转动菜单
     */
    private boolean enable;

    private int selectedPosition = 0;

    //    private ScrollHelper scroller;
    private AnimatorValue scrollAnimatorValue;

    private boolean isTouch = false;

    private OnSpinSelectedListener onSpinSelectedListener;

    private OnMenuSelectedListener onMenuSelectedListener;

    public SpinMenuLayout(Context context) {
        this(context, null);
    }

    public SpinMenuLayout(Context context, AttrSet attrs) {
        this(context, attrs, null);
    }

    public SpinMenuLayout(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setTouchEventListener(this);
        setEstimateSizeListener(this);
        setLayoutRefreshedListener(this);
        scrollAnimatorValue = new AnimatorValue();
        scrollAnimatorValue.setDuration(300);
        scrollAnimatorValue.setCurveType(Animator.CurveType.DECELERATE);
    }


    /**
     * 计算最小和最大惯性滚动角度
     */
    private void computeFlingLimitAngle() {
        // 因为中心点在底边中点（坐标系相反），故这里计算的min和max与实际相反
        minFlingAngle = isCyclic ? Integer.MIN_VALUE : -ANGLE_SPACE * (getChildCount() - 1);
        maxFlingAngle = isCyclic ? Integer.MAX_VALUE : 0;
    }

    /**
     * 依据当前触摸点坐标计算转动的角度
     *
     * @param xTouch
     * @param yTouch
     * @return 转动速度
     */
    private float computeAngle(float xTouch, float yTouch) {
        // 圆心点在底边的中点上，根据圆心点转化为对应坐标x, y
        float x = Math.abs(xTouch - getEstimatedWidth() / 2);
        float y = Math.abs(getEstimatedHeight() - yTouch);
        return (float) (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
    }

    /**
     * 计算自动滚动结束时角度值
     *
     * @param remainder
     * @return 结束时角度值
     */
    private int computeDistanceToEndAngle(int remainder) {
        int endAngle;
        if (remainder > 0) {
            if (Math.abs(remainder) > ANGLE_SPACE / 2) {
                if (perAngle < 0) { // 逆时针
                    endAngle = ANGLE_SPACE - remainder;
                } else { // 顺时针
                    endAngle = ANGLE_SPACE - Math.abs(remainder);
                }
            } else {
                endAngle = -remainder;
            }
        } else {
            if (Math.abs(remainder) > ANGLE_SPACE / 2) {
                if (perAngle < 0) {
                    endAngle = -ANGLE_SPACE - remainder;
                } else {
                    endAngle = Math.abs(remainder) - ANGLE_SPACE;
                }
            } else {
                endAngle = -remainder;
            }
        }
        return endAngle;
    }

    private int computeClickToEndAngle(int clickIndex, int currSelPos) {
        int endAngle;
        if (isCyclic) {
            clickIndex = clickIndex == 0 && currSelPos == getMenuItemCount() - 1 ? getMenuItemCount() : clickIndex;
            currSelPos = currSelPos == 0 && clickIndex != 1 ? getMenuItemCount() : currSelPos;
        }
        endAngle = (currSelPos - clickIndex) * ANGLE_SPACE;
        return endAngle;
    }

    /**
     * 获取当前选中的位置
     *
     * @return 当前选择position
     */
    public int getSelectedPosition() {
        return selectedPosition;
    }

    /**
     * 获取圆形转动菜单的真正半径<br/>
     * 半径是依据 child 的高度加上 SpinMenuLayout 的宽度的一半<br/>
     * 所以当没有 child 的时候，半径取值为 -1
     *
     * @return 获取半径
     */
    public int getRealRadius() {
        if (getChildCount() > 0) {
            return getEstimatedWidth() / 2 + getComponentAt(0).getHeight();
        } else {
            return -1;
        }
    }

    public int getMaxMenuItemCount() {
        return 360 / ANGLE_SPACE;
    }

    public int getMenuItemCount() {
        return getChildCount();
    }

    public boolean isCyclic() {
        return isCyclic;
    }

    public void postEnable(boolean isEnable) {
        enable = isEnable;
    }

    public void setOnSpinSelectedListener(OnSpinSelectedListener listener) {
        onSpinSelectedListener = listener;
    }

    public void setOnMenuSelectedListener(OnMenuSelectedListener listener) {
        onMenuSelectedListener = listener;
    }

    @Override
    public void onClick(Component view) {
        int index = getChildIndex(view);
        int selPos = getSelectedPosition();
        if (Math.abs(perAngle) <= touchSlopAngle) {
            if (index != selPos) {
                final float startAngle = delayAngle;
                final float endAngle = startAngle + computeClickToEndAngle(index, selPos);

                scrollAnimatorValue.setValueUpdateListener((animatorValue, v) -> {
                    delayAngle = startAngle + (endAngle - startAngle) * v;
                    postLayout();
                });
                scrollAnimatorValue.start();
            } else {
                if (view instanceof SMItemLayout
                        && onMenuSelectedListener != null
                        && enable) {
                    onMenuSelectedListener.onMenuSelected((SMItemLayout) view);
                }
            }
        }
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        float curX = getTouchX(touchEvent, 0);
        float curY = getTouchY(touchEvent, 0);
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                if (!enable) return false;
                isTouch = true;
                preX = curX;
                preY = curY;
                preTimes = System.currentTimeMillis();
                perAngle = 0;
                anglePerSecond = 0;
                if (scrollAnimatorValue.isRunning()) {
                    scrollAnimatorValue.stop();
                }
                break;
            case TouchEvent.POINT_MOVE:
                if (!enable) return false;
                float diffX = curX - preX;
                float start = computeAngle(preX, preY);
                float end = computeAngle(curX, curY);

                float perDiffAngle;
                if (diffX > 0) {
                    perDiffAngle = Math.abs(start - end);
                } else {
                    perDiffAngle = -Math.abs(end - start);
                }
                if (!isCyclic && (delayAngle < minFlingAngle || delayAngle > maxFlingAngle)) {
                    // 当前不是循环滚动模式，且转动的角度超出了可转角度的范围
                    perDiffAngle /= DELAY_ANGLE_RATIO;
                }
                delayAngle += perDiffAngle;
                perAngle = perDiffAngle;
                anglePerSecond = perAngle * 1000 / (System.currentTimeMillis() - preTimes);
                preTimes = System.currentTimeMillis();


                preX = curX;
                preY = curY;
                postLayout();
                break;
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
                isTouch = false;

                int startAngle = (int) delayAngle;
                int postAngle = startAngle;
                if (Math.abs(anglePerSecond) > MIN_PER_ANGLE && startAngle >= minFlingAngle && startAngle <= maxFlingAngle) {
                    postAngle = computeDistanceToEndAngle(startAngle % ANGLE_SPACE) + ((int) (anglePerSecond * ACCELERATE_ANGLE_RATIO * scrollAnimatorValue.getDuration() / 1000 / 2)) / ANGLE_SPACE * ANGLE_SPACE;
                } else {
                    postAngle = computeDistanceToEndAngle(startAngle % ANGLE_SPACE);
                }
                anglePerSecond = 0;
                if (!isCyclic) { // 当不是循环转动时，需要校正角度
                    if (postAngle + startAngle >= maxFlingAngle) {
                        postAngle = maxFlingAngle - startAngle;
                    } else if (startAngle + postAngle < minFlingAngle) {
                        postAngle = minFlingAngle - startAngle;
                    }
                }

                final int endAngle = startAngle + postAngle;
                if (startAngle != endAngle) {
                    scrollAnimatorValue.setValueUpdateListener((animatorValue, v) -> {
                        delayAngle = startAngle + (endAngle - startAngle) * v;
                        postLayout();
                    });
                    scrollAnimatorValue.start();
                } else {
                    delayAngle = endAngle;
                    postLayout();
                }
                break;
        }
        return true;
    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        // 宽度、高度与父容器一致
        ComponentContainer parent = ((ComponentContainer) getComponentParent());
        int measureWidth = parent.getEstimatedWidth();
        int measureHeight = parent.getEstimatedHeight();
        setEstimatedSize(measureWidth, measureHeight);
        return false;
    }

    private float getTouchX(TouchEvent touchEvent, int index) {
        float touchX = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                touchX = touchEvent.getPointerScreenPosition(index).getX() - xy[0];
            } else {
                touchX = touchEvent.getPointerPosition(index).getX();
            }
        }
        return touchX;
    }

    private float getTouchY(TouchEvent touchEvent, int index) {
        float touchY = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                touchY = touchEvent.getPointerScreenPosition(index).getY() - xy[1];
            } else {
                touchY = touchEvent.getPointerPosition(index).getY();
            }
        }
        return touchY;
    }


    private void calculatorSelectionPosition() {
        float currentAngle = delayAngle % 360;
        if (currentAngle < 0) {
            currentAngle += 360;
        }
        float selectAngle = 360 - currentAngle + ANGLE_SPACE / 2;
        if (selectAngle >= 360) {
            selectAngle -= 360;
        }

        selectedPosition = (int) selectAngle / ANGLE_SPACE;

        if (!isTouch && !scrollAnimatorValue.isRunning() && onSpinSelectedListener != null) {
            onSpinSelectedListener.onSpinSelected(selectedPosition);
        }
    }

    @Override
    public void onRefreshed(Component component) {
        final int childCount = getChildCount();
        if (childCount <= 0) return;

        isCyclic = getChildCount() == 360 / MIN_PER_ANGLE;
        computeFlingLimitAngle();

        delayAngle %= 360.f;
        float startAngle = delayAngle;
        calculatorSelectionPosition();
        Component child;
        int childWidth, childHeight;
        int centerX = getEstimatedWidth() / 2;
        int centerY = getEstimatedHeight();

        radius = centerX * RADIUS_HALF_WIDTH_RATIO + getComponentAt(1).getEstimatedHeight() / 2;
        for (int i = 0; i < childCount; i++) {
            child = getComponentAt(i);
            childWidth = child.getEstimatedWidth();
            childHeight = child.getEstimatedHeight();

            int left = (int) (centerX + Math.sin(Math.toRadians(startAngle)) * radius);
            int top = (int) (centerY - Math.cos(Math.toRadians(startAngle)) * radius);

            child.setTranslation(left - childWidth / 2, top - childHeight / 2);
            child.setClickedListener(this);
            child.setRotation(startAngle);
            startAngle += ANGLE_SPACE;
        }
    }
}
